package org.dsun.json.read;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import org.dsun.json.bean.JsonElement;
import org.dsun.json.bean.JsonObject;
import org.dsun.json.bean.JsonValue;
import org.dsun.json.bean.ObjectType;
import org.dsun.json.bean.ValueList;
import org.dsun.json.exception.JsonReadException;
import org.dsun.json.util.NodeStack;

/**
 * 一帘幽梦同谁近，千古情人笑我痴！
 * @author donga
 * @version 1.2
 * @since 2012-10-24
 */
public class ReadJson {
	
	private InputStream fileInput;//json文件
	private String coding;
	private BufferedReader reader;//文件读取器
	private int row;//表示当前在解析的行
	private int col;//表示当前要解析的字符位置，从1开始
	private String line;//表示当前在解析的一行字符串
	private JsonValue json;//表示解析json后生成的对象树
	private NodeStack nodeStack=new NodeStack();//表示解析中还未完成的解析对象的堆栈
	private StringBuilder stringCache=new StringBuilder();//表示当前正在解析的字符串的缓存
	private ObjectType type=ObjectType.START;//表示解析器上一次解析出的类型
	
	/*构造函数*/
	public ReadJson(InputStream fileInput){
		this.fileInput=fileInput;
	}
	
	public ReadJson(InputStream fileInput,String coding){
		this.fileInput=fileInput;
		this.coding=coding;
	}
	
	/**
	 * 从json中读取信息
	 */
	public JsonValue read() throws FileNotFoundException, UnsupportedEncodingException{
		init();
		char c;
		while((line!=null && col<=line.length()) || readLine()){
			c=line.charAt(col-1);
			switch(type){
			case START:
				parseStart(c);
				break;
			case COLON:
				parseColon(c);
				break;
				
			case END:
				parseEnd(c);
				break;
				
			case COMMA:
				parseComma(c);
				break;
				
			case OBJECT:
				parseObject(c);
				break;
				
			case LIST:
				parseList(c);
				break;
				
			case OVER:
				parseOver(c);
				break;

			default:
				clearStringCache();
				readUntilSpace();
				throw new JsonReadException(row, col-stringCache.length()+1, "The value : "+stringCache+" is not allowed");
			}
		}
		return json;
	}
	
	/**
	 * 开始json解析
	 * @param c
	 */
	private void parseStart(char c){
		JsonValue jv;
		String parseString;
		switch (c) {
		case '{':
		case '[':
			clearStringCache();
			if(c=='{'){
				jv=new JsonValue(new JsonObject());
				type=ObjectType.OBJECT;
			}else{
				jv=new JsonValue(new ValueList());
				type=ObjectType.LIST;
			}
			json=jv;
			nodeStack.push(type, jv);
			col++;
			skipSpace();
			break;
			
		case '"':
			col++;
			clearStringCache();
			readString();
			parseString=stringCache.toString();
			jv=new JsonValue(parseString);
			json=jv;
			col++;
			skipSpace();
			type=ObjectType.OVER;
			break;
			
		case '-':
		case '+':
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '.':
			clearStringCache();
			readUntilSpace();
			parseString=stringCache.toString();
			try{
				if(parseString.indexOf('.')!=-1 || parseString.indexOf('e')!=-1 ||parseString.indexOf('E')!=-1){
					jv=new JsonValue(Double.parseDouble(parseString));
					type=ObjectType.DOUBLE;
				}else{
					String begins="";
					if(parseString.startsWith("+")){
						parseString=parseString.substring(1);
					} else if (parseString.startsWith("-")){
						begins="-";
						parseString=parseString.substring(1);
					}
					if(parseString.startsWith("0X") || parseString.startsWith("0X")){
						jv=new JsonValue(Integer.parseInt(begins+parseString.substring(2), 16));
					}else if(parseString.startsWith("0")){
						jv=new JsonValue(Integer.parseInt(begins+parseString.substring(1), 8));
					}else{
						jv=new JsonValue(Integer.parseInt(begins+parseString));
					}
					type=ObjectType.INT;
				}
				json=jv;
				skipSpace();
				type=ObjectType.OVER;
			}catch(Exception e){
				throw new JsonReadException(row, col-parseString.length()+1, "Wrong form of Number : "+parseString);
			}
			break;
			
		case 'n':
		case 'N':
		case 't':
		case 'T':
		case 'f':
		case 'F':
			clearStringCache();
			readUntilSpace();
			parseString=stringCache.toString();
			if("null".equalsIgnoreCase(parseString)){
				jv=new JsonValue();
				type=ObjectType.NUL;
			}else if("true".equalsIgnoreCase(parseString)){
				jv=new JsonValue(true);
				type=ObjectType.BOOLEAN;
			}else if("false".equalsIgnoreCase(parseString)){
				jv=new JsonValue(false);
				type=ObjectType.BOOLEAN;
			}else{
				throw new JsonReadException(row, col-stringCache.length()+1, "The value : \""+stringCache+"\" is not allowed");
			}
			json=jv;
			skipSpace();
			type=ObjectType.OVER;
			break;
			
		default:
			clearStringCache();
			readUntilSpace();
			throw new JsonReadException(row, col-stringCache.length()+1, "The value : \""+stringCache+"\" is not allowed");
		}
	}
	/**
	 * 解析冒号后面的信息
	 */
	private void parseColon(char c){
		JsonValue jv;
		String parseString;
		JsonElement je;
		switch (c) {
		case '{':
		case '[':
			clearStringCache();
			if(c=='{'){
				jv=new JsonValue(new JsonObject());
				type=ObjectType.OBJECT;
			}else{
				jv=new JsonValue(new ValueList());
				type=ObjectType.LIST;
			}
			je=new JsonElement((String)nodeStack.pop()[1], jv);
			((JsonObject)((JsonValue)nodeStack.getTop()[1]).getValue()).setProperty(je);
			nodeStack.push(type, jv);
			col++;
			skipSpace();
			break;
			
		case '"':
			col++;
			clearStringCache();
			readString();
			parseString=stringCache.toString();
			jv=new JsonValue(parseString);
			je=new JsonElement((String)nodeStack.pop()[1], jv);
			((JsonObject)((JsonValue)nodeStack.getTop()[1]).getValue()).setProperty(je);
			col++;
			skipSpace();
			type=ObjectType.END;
			break;
			
		case '-':
		case '+':
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '.':
			clearStringCache();
			readUntilSpace();
			parseString=stringCache.toString();
			try{
				if(parseString.indexOf('.')!=-1 || parseString.indexOf('e')!=-1 ||parseString.indexOf('E')!=-1){
					jv=new JsonValue(Double.parseDouble(parseString));
					type=ObjectType.DOUBLE;
				}else{
					String begins="";
					if(parseString.startsWith("+")){
						parseString=parseString.substring(1);
					} else if (parseString.startsWith("-")){
						begins="-";
						parseString=parseString.substring(1);
					}
					if(parseString.startsWith("0X") || parseString.startsWith("0X")){
						jv=new JsonValue(Integer.parseInt(begins+parseString.substring(2), 16));
					}else if(parseString.startsWith("0")){
						jv=new JsonValue(Integer.parseInt(begins+parseString.substring(1), 8));
					}else{
						jv=new JsonValue(Integer.parseInt(begins+parseString));
					}
					type=ObjectType.INT;
				}
				je=new JsonElement((String)nodeStack.pop()[1], jv);
				((JsonObject)((JsonValue)nodeStack.getTop()[1]).getValue()).setProperty(je);
				skipSpace();
				type=ObjectType.END;
			}catch(Exception e){
				throw new JsonReadException(row, col-parseString.length()+1, "Wrong form of Number : "+parseString);
			}
			break;
			
		case 'n':
		case 'N':
		case 't':
		case 'T':
		case 'f':
		case 'F':
			clearStringCache();
			readUntilSpace();
			parseString=stringCache.toString();
			if("null".equalsIgnoreCase(parseString)){
				jv=new JsonValue();
				type=ObjectType.NUL;
			}else if("true".equalsIgnoreCase(parseString)){
				jv=new JsonValue(true);
				type=ObjectType.BOOLEAN;
			}else if("false".equalsIgnoreCase(parseString)){
				jv=new JsonValue(false);
				type=ObjectType.BOOLEAN;
			}else{
				throw new JsonReadException(row, col-stringCache.length()+1, "The value : \""+stringCache+"\" is not allowed");
			}
			je=new JsonElement((String)nodeStack.pop()[1], jv);
			((JsonObject)((JsonValue)nodeStack.getTop()[1]).getValue()).setProperty(je);
			skipSpace();
			type=ObjectType.END;
			break;
			
		default:
			clearStringCache();
			readUntilSpace();
			throw new JsonReadException(row, col-stringCache.length()+1, "The value : \""+stringCache+"\" is not allowed");
		}
	}
	
	/**
	 * 结束标志后的信息的解析，一般为后期解析的准备工作
	 */
	private void parseEnd(char c){
		ObjectType ot;
		switch(c){
		case ':':
			if((ObjectType)nodeStack.getTop()[0]==ObjectType.KEY){
				type=ObjectType.COLON;
				col++;
				skipSpace();
			}else{
				throw new JsonReadException(row, col, "The ':' can't appear here");
			}
			break;
			
		case ',':
			ot=(ObjectType)nodeStack.getTop()[0];
			if(ot==ObjectType.OBJECT || ot==ObjectType.LIST){
				type=ObjectType.COMMA;
				col++;
				skipSpace();
			}else{
				throw new JsonReadException(row, col, "The ',' can't appear here");
			}
			break;
			
		case ']':
			if((ObjectType)nodeStack.getTop()[0]==ObjectType.LIST){
				nodeStack.pop();
				if(nodeStack.isEmpty()){
					type=ObjectType.OVER;
				}else{
					type=ObjectType.END;
				}
				col++;
				skipSpace();
			}else{
				throw new JsonReadException(row, col, "The ']' can't appear here");
			}
			break;
			
		case '}':
			if((ObjectType)nodeStack.getTop()[0]==ObjectType.OBJECT){
				nodeStack.pop();
				if(nodeStack.isEmpty()){
					type=ObjectType.OVER;
				}else{
					type=ObjectType.END;
				}
				col++;
				skipSpace();
			}else{
				throw new JsonReadException(row, col, "The '}' can't appear here");
			}
			break;
			
		default:
			clearStringCache();
			readUntilSpace();
			throw new JsonReadException(row, col-stringCache.length()+1, "The value : \""+stringCache+"\" is not allowed");
		}
	}
	
	/**
	 * 解析逗号后面的信息
	 */
	private void parseComma(char c){
		ObjectType ot=(ObjectType)nodeStack.getTop()[0];
		JsonValue stackJv=(JsonValue)nodeStack.getTop()[1];
		JsonValue jv;
		String parseString;
		switch(c){
		case '[':
		case '{':
			if(ot==ObjectType.LIST){
				if(c=='['){
					if(((ValueList)stackJv.getValue()).size()>0){
						JsonValue listElement=(JsonValue)((ValueList)stackJv.getValue()).get(0);
						if(!(listElement.getValue() instanceof ValueList)){
							throw new JsonReadException(row, col, "The '"+c+"' can't appear here");
						}
					}
					jv=new JsonValue(new ValueList());
					type=ObjectType.LIST;
				}else{
					if(((ValueList)stackJv.getValue()).size()>0){
						JsonValue listElement=(JsonValue)((ValueList)stackJv.getValue()).get(0);
						if(!(listElement.getValue() instanceof JsonObject)){
							throw new JsonReadException(row, col, "The '"+c+"' can't appear here");
						}
					}
					jv=new JsonValue(new JsonObject());
					type=ObjectType.OBJECT;
				}
				((ValueList)stackJv.getValue()).add(jv);
				nodeStack.push(type, jv);
				col++;
				skipSpace();
			}else{
				throw new JsonReadException(row, col, "The '"+c+"' can't appear here");
			}
			break;
		
		case '-':
		case '+':
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '.':
			clearStringCache();
			readUntilSpace();
			parseString=stringCache.toString();
			if(ot==ObjectType.LIST){
				try{
					if(((ValueList)stackJv.getValue()).size()>0){
						JsonValue listElement=(JsonValue)((ValueList)stackJv.getValue()).get(0);
						if(!(listElement.getValue() instanceof Integer) && !(listElement.getValue() instanceof Double)){
							throw new JsonReadException(row, col, "The '"+c+"' can't appear here");
						}
					}
					if(parseString.indexOf('.')!=-1 || parseString.indexOf('e')!=-1 ||parseString.indexOf('E')!=-1){
						jv=new JsonValue(Double.parseDouble(parseString));
						type=ObjectType.DOUBLE;
					}else{
						String begins="";
						if(parseString.startsWith("+")){
							parseString=parseString.substring(1);
						} else if (parseString.startsWith("-")){
							begins="-";
							parseString=parseString.substring(1);
						}
						if(parseString.startsWith("0X") || parseString.startsWith("0X")){
							jv=new JsonValue(Integer.parseInt(begins+parseString.substring(2), 16));
						}else if(parseString.startsWith("0")){
							jv=new JsonValue(Integer.parseInt(begins+parseString.substring(1), 8));
						}else{
							jv=new JsonValue(Integer.parseInt(begins+parseString));
						}
						type=ObjectType.INT;
					}
					((ValueList)((JsonValue)nodeStack.getTop()[1]).getValue()).add(jv);
					skipSpace();
					type=ObjectType.END;
				}catch(Exception e){
					throw new JsonReadException(row, col-parseString.length()+1, "Wrong form of Number "+parseString);
				}
			}else{
				throw new JsonReadException(row, col-parseString.length()+1, "The value : \""+parseString+"\" can't appear here");
			}
			break;
			
		case 'n':
		case 'N':
		case 't':
		case 'T':
		case 'f':
		case 'F':
			clearStringCache();
			readUntilSpace();
			parseString=stringCache.toString();
			if(ot==ObjectType.LIST){
				if(((ValueList)stackJv.getValue()).size()>0){
					JsonValue listElement=(JsonValue)((ValueList)stackJv.getValue()).get(0);
					if(!(listElement.getValue() instanceof Boolean)){
						throw new JsonReadException(row, col, "The '"+c+"' can't appear here");
					}
				}
				if("null".equalsIgnoreCase(parseString)){
					jv=new JsonValue();
					type=ObjectType.NUL;
				}else if("true".equalsIgnoreCase(parseString)){
					jv=new JsonValue(true);
					type=ObjectType.BOOLEAN;
				}else if("false".equalsIgnoreCase(parseString)){
					jv=new JsonValue(false);
					type=ObjectType.BOOLEAN;
				}else{
					throw new JsonReadException(row, col-parseString.length()+1, "The value:"+parseString+" is not allowed");
				}
				((ValueList)((JsonValue)nodeStack.getTop()[1]).getValue()).add(jv);
				skipSpace();
				type=ObjectType.END;
			}else{
				throw new JsonReadException(row, col-parseString.length()+1, "The value : "+parseString+" is not allowed");
			}
			break;
			
		case '"':
			col++;
			clearStringCache();
			readString();
			parseString=stringCache.toString();
			if(ot==ObjectType.LIST){
				if(((ValueList)stackJv.getValue()).size()>0){
					JsonValue listElement=(JsonValue)((ValueList)stackJv.getValue()).get(0);
					if(!(listElement.getValue() instanceof String)){
						throw new JsonReadException(row, col, "The '"+c+"' can't appear here");
					}
				}
				jv=new JsonValue(parseString);
				((ValueList)((JsonValue)nodeStack.getTop()[1]).getValue()).add(jv);
			}else if(ot==ObjectType.OBJECT){
				if(parseString.length()>0){
					nodeStack.push(ObjectType.KEY, parseString);
				}else{
					throw new JsonReadException(row, col-parseString.length()+1,"key could not be null");
				}
			}else{
				throw new JsonReadException(row, col-parseString.length(), "The '\"' can't appear here");
			}
			col++;
			skipSpace();
			type=ObjectType.END;
			break;
			
		default:
			clearStringCache();
			readUntilSpace();
			throw new JsonReadException(row, col-stringCache.length()+1, "The value : "+stringCache+" is not allowed");
		}
	}
	
	/**
	 * 解析object标志{后的信息
	 */
	private void parseObject(char c){
		String parseString;
		if(c=='"'){
			col++;
			clearStringCache();
			readString();
			parseString=stringCache.toString();
			if(stringCache.length()>0){
				nodeStack.push(ObjectType.KEY, parseString);
				type=ObjectType.END;
				col++;
				skipSpace();
			}else{
				throw new JsonReadException(row, col-parseString.length()+1,"key could not be null");
			}
		}else if(c=='}'){
			nodeStack.pop();
			type=ObjectType.END;
			col++;
			skipSpace();
		}else{
			clearStringCache();
			readUntilSpace();
			throw new JsonReadException(row, col-stringCache.length()+1, "The string : \""+stringCache+"\" is not allowed");
		}
	}
	
	/**
	 * 解析object标志[后的信息
	 */
	private void parseList(char c){
		JsonValue jv;
		String parseString;
		switch(c){
		case '[':
		case '{':
			if(c=='['){
				jv=new JsonValue(new ValueList());
				type=ObjectType.LIST;
			}else{
				jv=new JsonValue(new JsonObject());
				type=ObjectType.OBJECT;
			}
			((ValueList)((JsonValue)nodeStack.getTop()[1]).getValue()).add(jv);
			nodeStack.push(type, jv);
			col++;
			skipSpace();
			break;
			
		case ']':
			nodeStack.pop();
			type=ObjectType.END;
			col++;
			skipSpace();
			break;
			
		case '-':
		case '+':
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '.':
			clearStringCache();
			readUntilSpace();
			parseString=stringCache.toString();
			try{
				if(parseString.indexOf('.')!=-1 || parseString.indexOf('e')!=-1 ||parseString.indexOf('E')!=-1){
					jv=new JsonValue(Double.parseDouble(parseString));
					type=ObjectType.DOUBLE;
				}else{
					String begins="";
					if(parseString.startsWith("+")){
						parseString=parseString.substring(1);
					} else if (parseString.startsWith("-")){
						begins="-";
						parseString=parseString.substring(1);
					}
					if(parseString.startsWith("0X") || parseString.startsWith("0X")){
						jv=new JsonValue(Integer.parseInt(begins+parseString.substring(2), 16));
					}else if(parseString.startsWith("0")){
						jv=new JsonValue(Integer.parseInt(begins+parseString.substring(1), 8));
					}else{
						jv=new JsonValue(Integer.parseInt(begins+parseString));
					}
					type=ObjectType.INT;
				}
				((ValueList)((JsonValue)nodeStack.getTop()[1]).getValue()).add(jv);
				skipSpace();
				type=ObjectType.END;
			}catch(Exception e){
				throw new JsonReadException(row, col-parseString.length()+1, "Wrong form of Number "+parseString);
			}
			break;
			
		case 'n':
		case 'N':
		case 't':
		case 'T':
		case 'f':
		case 'F':
			clearStringCache();
			readUntilSpace();
			parseString=stringCache.toString();
			if("null".equalsIgnoreCase(parseString)){
				jv=new JsonValue();
				type=ObjectType.NUL;
			}else if("true".equalsIgnoreCase(parseString)){
				jv=new JsonValue(true);
				type=ObjectType.BOOLEAN;
			}else if("false".equalsIgnoreCase(parseString)){
				jv=new JsonValue(false);
				type=ObjectType.BOOLEAN;
			}else{
				throw new JsonReadException(row, col-parseString.length()+1, "value:"+parseString+" is not allowed");
			}
			((ValueList)((JsonValue)nodeStack.getTop()[1]).getValue()).add(jv);
			skipSpace();
			type=ObjectType.END;
			break;
			
		case '"':
			col++;
			clearStringCache();
			readString();
			parseString=stringCache.toString();
			jv=new JsonValue(parseString);
			((ValueList)((JsonValue)nodeStack.getTop()[1]).getValue()).add(jv);
			col++;
			skipSpace();
			type=ObjectType.END;
			break;
			
		default:
			clearStringCache();
			readUntilSpace();
			throw new JsonReadException(row, col-stringCache.length()+1, "The value : "+stringCache+" is not allowed");
		}
	}
	
	/**
	 * 结束解析，去掉多余字符
	 * @param c
	 */
	private void parseOver(char c){
		if((c!=32) && (c!=9)){
			throw new JsonReadException(row, col+1, "The json should have be end before this place");
		}else{
			skipSpace();
		}
	}
	
	/**
	 * 消除最左边的空格和制表符
	 */
	private boolean skipSpace(){ 
		char tempc;
		boolean tag=false;
		for(;col<=line.length();col++){
			tempc=line.charAt(col-1);
			if((tempc!=32) && (tempc!=9)){
				break;
			}else{
				tag=true;
			}
		}
		return tag;
	}
	
	/**
	 * 读取文档中不为空，不为注解的下一行
	 * @return 读出下一行则返回true，否则放回false
	 */
	private boolean readLine(){
		try {
			do{
				row++;
				col=1;
				line=reader.readLine();
				if(line==null){
					return false;
				}else{
					skipSpace();
				}
				
			}while("".equals(line.substring(col-1)) || ((col<=line.length()-1)&&"//".equals(line.substring(col-1,col+1))));
//			System.out.println(line.substring(col-1));
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			throw new JsonReadException("Error read a line from the file");
		}
	}
	
	/**
	 * 为json读取做初始化工作
	 * @throws FileNotFoundException
	 * @throws UnsupportedEncodingException
	 */
	private void init() throws FileNotFoundException, UnsupportedEncodingException{
		if(fileInput==null){
			throw new JsonReadException("File is null");
		}
		if(coding==null){
			reader=new BufferedReader(new InputStreamReader(fileInput));
		}else{
			reader=new BufferedReader(new InputStreamReader(fileInput,coding));
		}
		json=null;
		type=ObjectType.START;
		clearStringCache();
		nodeStack.clear();
		row=0;
	}
	
	/**
	 * 读取字符串
	 */
	private void readString(){
		boolean escape=false;
		char tempc;
		type=ObjectType.STRING;
		for(;col<=line.length();col++){
			tempc=line.charAt(col-1);
			if(escape){
				switch(tempc){
				case '"':
					stringCache.append('"');
					break;
				case '\\':
					stringCache.append('\\');
					break;
				case '/':
					stringCache.append('/');
					break;
				case 'b':
				case 'B':
					stringCache.append((char)8);
					break;
				case 'f':
				case 'F':
					stringCache.append((char)12);
					break;
				case 'n':
				case 'N':
					stringCache.append((char)10);
					break;
				case 'r':
				case 'R':
					stringCache.append((char)13);
					break;
				case 't':
				case 'T':
					stringCache.append((char)9);
					break;
				default:
					throw new JsonReadException(row, col-1, "The Character '\\"+tempc+"' is not allowed");
				}
				escape=false;
			}else{
				if(tempc=='\\'){
					escape=true;
				}else{
					if(tempc=='"'){
						break;
					}
					stringCache.append(tempc);
					escape=false;
				}
			}
		}
		if(col>line.length()){
			throw new JsonReadException(row, col, "Need a semicolon to end");
		}
	}
	
	/**
	 * 读取信息直到下一个分隔点
	 */
	private void readUntilSpace(){
		char tempc;
		for(;col<=line.length();col++){
			tempc=line.charAt(col-1);
			if((tempc!=32) && (tempc!=9)&&(tempc!=',')&&(tempc!=']')&&(tempc!='}')&&(tempc!=':')){
				stringCache.append(tempc);
			}else{
				if(stringCache.length()==0)
					stringCache.append(tempc);
				break;
			}
		}
	}
	
	/**
	 * 清空StringCache
	 */
	private void clearStringCache(){
		if(stringCache.length()>0){
			stringCache.delete(0, stringCache.length());
		}
	}
	
}
